//
// Created by DELL on 2023/4/27.
//
#include "quickjspp.hpp"
#include "quickjs-libc.h"
#include <iostream>

class ArrayXY
{
private:
    double x = 0.0, y = 0.0;
    double width = 100.0, height = 100.0;
public:
    ArrayXY()
    { }

    ArrayXY(double w, double h): width(w), height(h)
    { }

    void show() const
    {
        std::cout << " [ArrayXY Object] x = " << x << " ; y = " << y
                  << " ; width = " << width << " height = " << height
                  << '\n';
    }

    void set_width(double width)
    {
        this->width = width;
        std::fprintf(stdout, " [ArrayXY] Width set to %f \n", width);

    }

    void set_height(double height)
    {
        this->height = height;
        std::fprintf(stdout, " [ArrayXY] Height set to %f \n", height);
    }

    double get_height() const { return this->height; }
    double get_width () const { return this->width; }

    void show_points(std::vector<double> const& points)
    {
        std::cout << " [ArrayXY] Plotting points =>> ";
        for(auto p : points) { std::cout << " " << p; }
        std::cout << "\n";
    }
};

int main(int argc, char** argv)
{
    std::cout << " [INFO] Started Ok" << std::endl;
    using namespace qjs;
    Runtime runtime;
    Context context(runtime);
    js_std_init_handlers(runtime.rt);

    /* loader for ES6 modules */
    JS_SetModuleLoaderFunc(runtime.rt, nullptr, js_module_loader, nullptr);

    js_std_add_helpers(context.ctx, argc - 1, argv + 1);

    /* system modules */
    js_init_module_std(context.ctx, "std");
    js_init_module_os(context.ctx, "os");


    std::fprintf(stderr, " [TRACE] Before loading code. \n");

    //Demo1 运行普通JS脚本
    const char* str = R"(
            console.log(" [QuickJS] => =>> Script loaded. Ok. \n");

            for(n = 1; n <= 5; n++){
                console.log(` [QuickJS-TRACE] n = ${n}/5 `);
            }

            // ----- Define user variables here ----

            asset_path = "d:\\Users\\mydir\\data\\blackjack.txt";
            game_score = 0.251 ;

            let x = 10.3 ;
            datapoints = [ 0.25 ,  9.23 , -3.26 , 20.12 ];

            console.log(`\n  [QuickJS] asset_path = ${asset_path}` );
            console.log(`   [QuickJS] score = ${100.0 * game_score} (in percent) \n`);
            console.log(`   [QuickJS] data points = ${datapoints} `)
      )";

    //Demo2 运行含有mudule调用的脚本
    const char*str1 = R"(
		import { fib } from "./fib_module.js";//测试quickjs的examples的fib_module模块
		console.log("Hello World");
		console.log("fib(10)=", fib(10));
		var st=Date.now();
		console.log("fib(23)=", fib(23));
		var end=Date.now();
		console.log("time=", (end-st)/1000.);
	)";

    try
    {
//        context.eval(str); //, "", JS_EVAL_TYPE_MODULE); //普通调用
//        context.eval(str1, "<eval>", JS_EVAL_TYPE_MODULE);//含有module的调用
    } catch( const qjs::exception& ex)
    {
        auto exc = context.getException();
        std::cerr << (exc.isError() ? "Error: " : "Throw: ") << (std::string)exc << std::endl;
        if((bool)exc["stack"])
            std::cerr << (std::string)exc["stack"] << std::endl;

        js_std_free_handlers(runtime.rt);
        return 1;
    }

    std::fprintf(stderr, " [TRACE] After loading code. \n");

    //Demo3 运行JS语句的结果返回宿主C++
    double number = (double) context.eval(" 10.2 * (3.1 + 1 + 10 ) - 10.12 * 2.3", "<eval>", JS_EVAL_TYPE_MODULE);
    std::cout << " [RESULT] number = " << number << '\n';

    //Demo4 宿主端读取JS端运行时内存在的变量
    std::puts("\n [*] ===== Read configuration variables defined in the js code. ====\n");
    {
        auto var_asset_path = context.global()["asset_path"].as<std::string>();
        std::cout << "    =>> asset_path = " << var_asset_path << '\n';

//        auto score = context.global()["game_score"].as<double>();
//        std::cout << "    =>> game_score (%) = " << 100.0 * score << '\n';

//        auto points = context.global()["datapoints"].as<std::vector<double>>();
//        std::cout << "    ==>> datapoints = [" << points.size() << "]( ";
//        for(auto p : points) {  std::cout << p << ' '; }
//        std::cout << " ) \n";
    }

    std::puts("\n [*] ===== Define variables in C++-side  ====\n");
    {
        //Demo5 宿主C++端定义并传入JS变量，然后JS端运行时内获取并运行
        context.global()["user_name"]   = context.newValue("myastrotong");
        context.global()["user_points"] = context.newValue(9527);

        auto data = std::vector<std::string>{ "ADA", "RUST", "C++11", "C++17", "C++20"
                                             , "JavaScript", "OCaml", "C#(Csharp)" };

        context.global()["user_data"] = context.newValue(data);

        // Note: This code should be within an exception handler.
        context.eval(R"(
            console.log(` [STEP 2] user_name = ${user_name} ; points = ${user_points} `);
            console.log(` [STEP 2] user_data = ${user_data} ; type = ${ typeof(user_data) } `);
            console.log(` [STEP 2] user_data[5] = ${ user_data[5] } `)

            // Iterate over the array
            for(let x in user_data){ console.log(user_data[x]); }
        )");

    }

    //Demo6 宿主C++端定义JS Module，然后JS来调用该module
    std::puts("\n [*] ===== Register class ArrayXY   ====\n");

    auto& module = context.addModule("chart");
    module.class_<ArrayXY>("ArrayXY")
        .constructor()
        .constructor<double, double>()
        .fun<&ArrayXY::show>("show")
        .fun<&ArrayXY::set_height>("set_height")
        .fun<&ArrayXY::set_width>("set_width")
        .fun<&ArrayXY::show_points>("show_points")
        .property<&ArrayXY::get_width,  &ArrayXY::set_width>("width")
        .property<&ArrayXY::get_height, &ArrayXY::set_height>("height")
        ;

//    module.add("user_path", "d:/Users/data/assets/game/score/marks");
    module.add("user_points", 9527);
    //module新增一个函数
    module.function("myfunc", [](double x, double y){ return 4.61 * x + 10 * y * y; });

    const char* module_code = R"(
        import { ArrayXY } from "chart";

        import * as chart from "chart"

//        console.log(` [SCRIPT] chart.user_path = ${chart.user_path} \n\n`);
        console.log(` [SCRIPT] chart.user_points = ${chart.user_points} \n\n`);

        console.log(` [SCRIPT] Result = ${ chart.myfunc(5.61, 9.821) } \n`);

        let ch = new ArrayXY(200, 600);
        ch.show();

        ch.set_width(800.0);
        ch.set_height(700.0)
        ch.show();

        console.log("   [QuickJS] Change ArrayXY dimensions using properties ");
        ch.width = 200;
        ch.height = 450;

        console.log(`\n   <QuickJS> ArrayXY width = ${ch.width} ; Chart height = ${ch.height} \n`);

        ch.show_points( [ 0.5 , 5.2 , -10.2 , -7.2, 41.123, 29.14 ] );
    )";

    try
    {
        context.eval(module_code, "<eval>", JS_EVAL_TYPE_MODULE);//含有module的调用
    }
    catch (const qjs::exception& ex)
    {
        auto exc = context.getException();
        std::cerr << (exc.isError() ? "Error: " : "Throw: ") << (std::string)exc << std::endl;
        if ((bool)exc["stack"])
            std::cerr << (std::string)exc["stack"] << std::endl;

        js_std_free_handlers(runtime.rt);
        return 1;
    }


    js_std_loop(context.ctx);
    // ----- Shutdown virtual machine ---------------//
    js_std_free_handlers(runtime.rt);

    return 0;
}